﻿using System;
using System.Diagnostics;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Entity.Scene;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;

namespace RayDen.RayEngine.Engines.PathTracer.PathSamplers
{
    public abstract class PathTracerPathBase<TPathState> : PathSamplerBase
        where TPathState : struct, IConvertible
    {
        public TPathState PathState;

        internal RgbSpectrum Throughput;
        protected int depth, tracedShadowRayCount;
        protected float pathWeight;
        protected bool specularBounce;

        public UInt64 PathExpression;

        protected RayEngineScene scene;
        protected SurfaceIntersectionData hitInfo = null;

        public int MaxRaysPerPath
        {
            get;
            set;
        }

        protected internal struct ShadowRayInfo
        {
            public float pdf;
            public RgbSpectrum color;
            public RayData shadowRay;
            public int currentShadowRayIndex;
        }

        protected ShadowRayInfo[] secRays;

        public override void InitPath(IPathProcessor buffer)
        {
            base.InitPath(buffer);
            this.scene = pathIntegrator.Scene;
            this.Radiance = new RgbSpectrum(0f);
            this.Throughput = new RgbSpectrum(1f);
            this.secRays = new ShadowRayInfo[scene.ShadowRayCount];
            this.Sample = pathIntegrator.Sampler.GetSample(null);
            IRay ray;
            pathIntegrator.Scene.Camera.GetRay(Sample.imageX, Sample.imageY, out ray);
            this.PathRay = (RayData)ray;
            this.RayIndex = -1;
            this.pathWeight = 1.0f;
            this.tracedShadowRayCount = 0;
            this.depth = 0;
            this.specularBounce = true;
        }

        public override bool FillRayBuffer(RayBuffer rayBuffer)
        {
            var leftSpace = rayBuffer.LeftSpace();
            if (leftSpace < this.RaysOccupied())
            {
                return false;
            }
            EnqueueRays(rayBuffer);
            return true;
        }

        protected bool ProcessShadowRays;

        public override void Advance(RayBuffer rayBuffer, SampleBuffer consumer)
        {
#if VERBOSE
            try {
#endif
            base.Advance(rayBuffer, consumer);

            if (OnSecondaryRays(rayBuffer, consumer))
            {
                return;
            }

            var rayHit = rayBuffer.rayHits[RayIndex];
            var wo = -PathRay.Dir;

            if (rayHit.Miss())
            {
                this.OnEnvironmentHit(ref wo, rayBuffer, consumer);

                Splat(consumer);
                return;
            }

            if (hitInfo == null)
            {
                hitInfo = SurfaceSampler.GetIntersection(ref PathRay, ref rayHit);
            }
            else
            {
                SurfaceSampler.GetIntersection(ref PathRay, ref rayHit, ref hitInfo);
            }
            var currentTriangleIndex = (int)rayHit.Index;
            tracedShadowRayCount = 0;

            if (hitInfo == null)
            {
                Debugger.Break();
            }

            if (hitInfo.IsLight)
            {
                if (OnLightHit(currentTriangleIndex, wo))
                {
                    Splat(consumer);

                    return;
                }
            }
            var hitPoint = PathRay.Point(rayHit.Distance);
            if (hitInfo.MMaterial.IsDiffuse())
            {
                if (!this.SampleSecondaryRays(ref hitPoint, ref wo))
                {
                    Splat(consumer);
                    return;
                }
            }

            float fPdf = 0f;
            var wi = new Vector();
            RgbSpectrum f;

            f = hitInfo.MMaterial.Sample_f(ref wo, out wi, ref hitInfo.Normal, ref hitInfo.ShadingNormal, ref Throughput,
                                Sample.GetLazyValue(), Sample.GetLazyValue(),
                                Sample.GetLazyValue(), ref hitInfo.TextureData,
                                out fPdf, out specularBounce);

            if (!OnBsdfSampling(ref f, ref fPdf))
            {
                Splat(consumer);
                return;
            }

            if (!ExtendPath(ref f, scene.RussianRuletteImportanceCap))
            {
                Splat(consumer);
                return;
            }

            PathRay.Org = hitPoint;
            PathRay.Dir = wi.Normalize();
#if VERBOSE

            }
            catch (Exception ex) {
                RayDen.Library.Components.SystemComponents.Tracer.TraceLine(ex.Message);
                RayDen.Library.Components.SystemComponents.Tracer.TraceLine(ex.StackTrace);
                throw;
            }
#endif
        }

        protected virtual bool OnBsdfSampling(ref RgbSpectrum f, ref float fPdf)
        {
            throw new NotImplementedException();
        }

        protected virtual bool ExtendPath(ref RgbSpectrum f, float p)
        {
            throw new NotImplementedException();
        }

        protected virtual bool SampleSecondaryRays(ref Point hitPoint, ref Vector wo)
        {
            throw new NotImplementedException();
        }

        protected virtual bool OnLightHit(int currentTriangleIndex, Vector wo)
        {
            if (specularBounce || depth == 1)
            {
                var lt = scene.GetLightByIndex(currentTriangleIndex);
                if (lt != null)
                {
                    var le = (RgbSpectrum)(lt.Le(ref wo));
                    Radiance += Throughput * le;
                }
            }
            return true;
        }

        protected virtual bool OnSecondaryRays(RayBuffer rayBuffer, SampleBuffer sb)
        {
            throw new NotImplementedException();
        }


        protected abstract int RaysOccupied();

        protected abstract void EnqueueRays(RayBuffer rb);


        protected abstract void OnEnvironmentHit(ref Vector dir, RayBuffer rayBuffer, SampleBuffer consumer);

        protected override bool ShadowRayTest(ref RayHit shadowRayHit, ref RayData shadowRay, out RgbSpectrum attenuation, out bool continueTrace)
        {
            var hit = shadowRayHit.Index == 0xffffffffu || scene.IsLight((int)shadowRayHit.Index);
            attenuation = new RgbSpectrum(1f);
            continueTrace = false;

            if (hit) return true;
            var mesh = scene.GetMeshByTriangleIndex((int)shadowRayHit.Index);
            var mat =
                //SurfaceMaterials.CreateMaterial(scene.MaterialProvider.Get(mesh.MaterialName));
                scene.MatLib.GetSurfMat(mesh != null ? mesh.MaterialName : "default");

            if (mat.AlphaTexture != null)
            {
                continueTrace = true;
                attenuation = (RgbSpectrum)(mat.AlphaTexture as ConstTextureInfo).Color;
                hit = true;
                //mat.MaterialData.Kt.Filter() < Sample.GetLazyValue();
            }

            return hit;
        }

        protected RgbSpectrum SampleEnvironment(Vector vector)
        {
            return this.scene.SampleEnvironment(-vector);
        }
    }
}